package br.com.fatecjp.pooai.gerenciador.taxi.agent;

import jade.MicroBoot;
import jade.core.AID;
import jade.core.Agent;
import jade.core.MicroRuntime;
import jade.core.behaviours.CyclicBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.com.fatecjp.pooai.gerenciador.taxi.ambiente.Ambiente;
import br.com.fatecjp.pooai.gerenciador.taxi.ambiente.Posicao;
import br.com.fatecjp.pooai.gerenciador.taxi.constants.ConstantesAmbiente;
import br.com.fatecjp.pooai.gerenciador.taxi.core.Cliente;
import br.com.fatecjp.pooai.gerenciador.taxi.core.Taxi;
import br.com.fatecjp.pooai.gerenciador.taxi.message.ConteudoMensagemAgente;
import br.com.fatecjp.pooai.gerenciador.taxi.message.TipoMensagemAgente;
import br.com.fatecjp.pooai.gerenciador.taxi.message.parser.ConteudoMensagemAgenteParser;
import br.com.fatecjp.pooai.gerenciador.taxi.message.parser.RequisicaoInformeBuscaClienteParser;
import br.com.fatecjp.pooai.gerenciador.taxi.message.parser.RequisicaoNotificacaoMovimentoTaxiParser;
import br.com.fatecjp.pooai.gerenciador.taxi.message.parser.RequisicaoSolicitacaoTaxiParser;
import br.com.fatecjp.pooai.gerenciador.taxi.message.parser.RequisicaoTransporteClienteParser;
import br.com.fatecjp.pooai.gerenciador.taxi.message.parser.RespostaRegistroTaxiParser;
import br.com.fatecjp.pooai.gerenciador.taxi.service.NomeServico;
import br.com.fatecjp.pooai.gerenciador.taxi.service.TipoServico;
import br.com.fatecjp.pooai.gerenciador.taxi.ui.AmbienteUI;
import br.com.fatecjp.pooai.gerenciador.taxi.ui.AmbienteUIDesktop;
import br.com.fatecjp.pooai.gerenciador.taxi.ui.ConfiguradorAmbiente;
import br.com.fatecjp.pooai.gerenciador.taxi.vo.ClienteNaoAtendidoVo;

public class AgenteGerenciador extends Agent {

	private static final Logger logger = LoggerFactory
			.getLogger(AgenteGerenciador.class);

	private static final long serialVersionUID = 1784217114424526642L;

	private Set<Taxi> taxisRegistrados;
	private Map<String, AID> idTaxisRegistrados;

	private Set<ClienteNaoAtendidoVo> clientesNaoAtendidos;

	private Ambiente ambiente;
	private AmbienteUI ambienteUI;

	private ConteudoMensagemAgenteParser conteudoMensagemParser;
	private RespostaRegistroTaxiParser respostaRegTaxiParser;
	private RequisicaoSolicitacaoTaxiParser reqSolTaxiParser;
	private RequisicaoNotificacaoMovimentoTaxiParser reqNotifMovTaxiParser;
	private RequisicaoInformeBuscaClienteParser reqInformeBuscaClienteParser;
	private RequisicaoTransporteClienteParser reqTranspClienteParser;

	public AgenteGerenciador() {
		this.taxisRegistrados = new LinkedHashSet<Taxi>();
		this.idTaxisRegistrados = new HashMap<String, AID>();
		this.conteudoMensagemParser = new ConteudoMensagemAgenteParser();
		this.respostaRegTaxiParser = new RespostaRegistroTaxiParser();
		this.reqSolTaxiParser = new RequisicaoSolicitacaoTaxiParser();
		this.reqNotifMovTaxiParser = new RequisicaoNotificacaoMovimentoTaxiParser();
		this.reqInformeBuscaClienteParser = new RequisicaoInformeBuscaClienteParser();
		this.clientesNaoAtendidos = new LinkedHashSet<ClienteNaoAtendidoVo>();
		this.reqTranspClienteParser = new RequisicaoTransporteClienteParser();
	}

	@Override
	protected void setup() {

		DFAgentDescription descricaoGerenciador = new DFAgentDescription();

		ServiceDescription servicoRequisicaoTaxi = new ServiceDescription();
		servicoRequisicaoTaxi
				.setType(TipoServico.GERENCIAMENTO_TAXI.toString());
		servicoRequisicaoTaxi.setName(NomeServico.SOLICITACAO_TAXI.toString());
		descricaoGerenciador.addServices(servicoRequisicaoTaxi);

		ServiceDescription servicoRegistroTaxi = new ServiceDescription();
		servicoRegistroTaxi.setType(TipoServico.GERENCIAMENTO_TAXI.toString());
		servicoRegistroTaxi.setName(NomeServico.REGISTRO_TAXI.toString());
		descricaoGerenciador.addServices(servicoRegistroTaxi);

		ServiceDescription servicoDesregistroTaxi = new ServiceDescription();
		servicoDesregistroTaxi.setType(TipoServico.GERENCIAMENTO_TAXI
				.toString());
		servicoDesregistroTaxi.setName(NomeServico.DESREGISTRO_TAXI.toString());
		descricaoGerenciador.addServices(servicoDesregistroTaxi);

		ServiceDescription servicoNotifMovTaxi = new ServiceDescription();
		servicoNotifMovTaxi.setType(TipoServico.GERENCIAMENTO_TAXI.toString());
		servicoNotifMovTaxi.setName(NomeServico.NOTIFICACAO_MOVIMENTO_TAXI
				.toString());
		descricaoGerenciador.addServices(servicoNotifMovTaxi);

		ServiceDescription servicoNotifInterCliente = new ServiceDescription();
		servicoNotifInterCliente.setType(TipoServico.GERENCIAMENTO_TAXI
				.toString());
		servicoNotifInterCliente
				.setName(NomeServico.NOTIFICACAO_INTERACAO_CLIENTE.toString());
		descricaoGerenciador.addServices(servicoNotifInterCliente);

		try {
			DFService.register(this, descricaoGerenciador);
		} catch (FIPAException e) {
			throw new RuntimeException(e);
		}

		logger.debug(String.format("O agente '%s' registrou os serviços no DF",
				getName()));

		this.addBehaviour(new GerenciadorTaxiBehaviour(this));

		final ConfiguradorAmbiente configuradorAmbiente = new ConfiguradorAmbiente();
		configuradorAmbiente.setVisible(true);
		configuradorAmbiente.setAlwaysOnTop(true);
		configuradorAmbiente
				.addSubmitButtonActionListener(new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {

						configuradorAmbiente.dispose();

						ambiente = new Ambiente();
						ambienteUI = new AmbienteUIDesktop(ambiente);

						MicroBoot.main(new String[] {});
						for (int i = 0; i < configuradorAmbiente
								.getQuantidadeTaxis(); i++) {
							try {
								MicroRuntime.startAgent("Taxi" + (i + 1),
										AgenteTaxi.class.getName(), null);
							} catch (Exception ex) {
								throw new RuntimeException(ex);
							}
						}

						ambienteUI.exibirUI();

					}

				});

		addBehaviour(new AtendimentoClientesBehaviour(this));

	}

	private class GerenciadorTaxiBehaviour extends CyclicBehaviour {

		private static final long serialVersionUID = 9044710834261419267L;

		public GerenciadorTaxiBehaviour(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {

			ACLMessage mensagemRecebida = myAgent.receive();
			if (mensagemRecebida != null) {
				ConteudoMensagemAgente conteudoMensagem = null;
				try {
					conteudoMensagem = conteudoMensagemParser
							.parse(mensagemRecebida.getContent());
				} catch (IllegalArgumentException e) {
					throw new RuntimeException(e);
				}
				if (mensagemRecebida.getPerformative() == ACLMessage.REQUEST) {
					if (conteudoMensagem != null) {

						if (conteudoMensagem.getTipoMensagem() == TipoMensagemAgente.REQUISICAO_TAXI
								.getCode()) {

							conteudoMensagem = reqSolTaxiParser
									.parse(mensagemRecebida.getContent());

							Posicao posicaoCliente = (Posicao) conteudoMensagem
									.getConteudo()[0];
							Posicao posicaoDestino = (Posicao) conteudoMensagem
									.getConteudo()[1];

							Cliente cliente = new Cliente(mensagemRecebida
									.getSender().getName());
							cliente.setPosicaoAtual(posicaoCliente);
							ambiente.adicionarCliente(cliente);
							ambiente.adicionarDestinoCliente(
									cliente.getIdentificacao(), posicaoDestino);
							ambienteUI.atualizarUI();

							clientesNaoAtendidos.add(new ClienteNaoAtendidoVo(
									cliente, posicaoCliente, posicaoDestino));

						} else if (conteudoMensagem.getTipoMensagem() == TipoMensagemAgente.REGISTRO_TAXI
								.getCode()) {

							/* Registrando o táxi */

							logger.debug(String.format("Táxi '%s' registrado",
									mensagemRecebida.getSender().getName()));

							Random geradorNumeros = new Random(
									new Date().getTime());

							ConteudoMensagemAgente conteudoResposta = new ConteudoMensagemAgente(
									TipoMensagemAgente.RESPOSTA_REGISTRO_TAXI
											.getCode());

							Integer posicaoXTaxi = geradorNumeros
									.nextInt(ConstantesAmbiente.NUMERO_POSICOES);
							Integer posicaoYTaxi = geradorNumeros
									.nextInt(ConstantesAmbiente.NUMERO_POSICOES);

							Posicao posicao = new Posicao(
									posicaoXTaxi.intValue(),
									posicaoYTaxi.intValue());

							conteudoResposta
									.setConteudo(new Object[] { posicao });

							Taxi novoTaxi = new Taxi(mensagemRecebida
									.getSender().getName());
							novoTaxi.setPosicaoAtual(posicao);
							ambiente.adicionarTaxi(novoTaxi);
							ambienteUI.atualizarUI();

							idTaxisRegistrados.put(novoTaxi.getIdentificacao(),
									mensagemRecebida.getSender());
							taxisRegistrados.add(novoTaxi);

							/* Enviando resposta à solicitação */
							ACLMessage resposta = mensagemRecebida
									.createReply();
							resposta.setPerformative(ACLMessage.CONFIRM);
							resposta.setContent(respostaRegTaxiParser
									.toString(conteudoResposta));
							myAgent.send(resposta);

						} else if (conteudoMensagem.getTipoMensagem() == TipoMensagemAgente.DESREGISTRO_TAXI
								.getCode()) {

							/* Desregistrando o táxi */
							taxisRegistrados.remove(ambiente
									.obterTaxi(mensagemRecebida.getSender()
											.getName()));
							idTaxisRegistrados.remove(mensagemRecebida
									.getSender().getName());

							ambiente.removerTaxi(mensagemRecebida.getSender()
									.getName());
							ambienteUI.atualizarUI();

							logger.debug(String.format(
									"Táxi '%s' desregistrado", mensagemRecebida
											.getSender().getName()));

							ACLMessage resposta = mensagemRecebida
									.createReply();
							resposta.setPerformative(ACLMessage.CONFIRM);
							myAgent.send(resposta);

						}

					}
				} else if (mensagemRecebida.getPerformative() == ACLMessage.INFORM) {
					if (conteudoMensagem != null) {

						if (conteudoMensagem.getTipoMensagem() == TipoMensagemAgente.NOTIFICACAO_MOVIMENTO_TAXI
								.getCode()) {

							conteudoMensagem = reqNotifMovTaxiParser
									.parse(mensagemRecebida.getContent());

							Posicao proximaPosicaoTaxi = (Posicao) conteudoMensagem
									.getConteudo()[1];

							boolean movimentoValido = true;
							for (Taxi statusTaxi : taxisRegistrados) {

								if (statusTaxi.getPosicaoAtual().equals(
										proximaPosicaoTaxi)
										&& !statusTaxi.getIdentificacao()
												.equals(mensagemRecebida
														.getSender().getName())) {
									movimentoValido = false;
									break;
								}

							}

							ACLMessage respostaSolMov = mensagemRecebida
									.createReply();
							if (movimentoValido) {
								ambiente.obterTaxi(
										mensagemRecebida.getSender().getName())
										.setPosicaoAtual(proximaPosicaoTaxi);
								ambienteUI.atualizarUI();
								respostaSolMov
										.setPerformative(ACLMessage.CONFIRM);
							} else {
								respostaSolMov
										.setPerformative(ACLMessage.REFUSE);
							}
							myAgent.send(respostaSolMov);

						} else if (conteudoMensagem.getTipoMensagem() == TipoMensagemAgente.REQUISICAO_INFORME_COLETA_CLIENTE
								.getCode()) {

							conteudoMensagem = reqTranspClienteParser
									.parse(mensagemRecebida.getContent());

							String identCliente = conteudoMensagem
									.getConteudo()[0].toString();

							ambiente.removerCliente(identCliente);
							ambienteUI.atualizarUI();

						} else if (conteudoMensagem.getTipoMensagem() == TipoMensagemAgente.REQUISICAO_INFORME_ENTREGA_CLIENTE
								.getCode()) {

							conteudoMensagem = reqTranspClienteParser
									.parse(mensagemRecebida.getContent());

							String identCliente = conteudoMensagem
									.getConteudo()[0].toString();

							ambiente.obterTaxi(
									mensagemRecebida.getSender().getName())
									.setOcupado(false);

							ambiente.removerDestinoCliente(identCliente);
							ambienteUI.atualizarUI();

						}

					}
				}
			} else {
				block();
			}

		}

	}

	private class AtendimentoClientesBehaviour extends CyclicBehaviour {

		private static final long serialVersionUID = 8509445213131429966L;

		public AtendimentoClientesBehaviour(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {

			if (!clientesNaoAtendidos.isEmpty()) {

				ClienteNaoAtendidoVo clienteNaoAtendido = clientesNaoAtendidos
						.iterator().next();

				Taxi taxiMaisProximo = null;
				double distanciaMaisProxima = 0d;
				for (Taxi taxi : taxisRegistrados) {
					if (!taxi.isOcupado()) {
						double distanciaTaxiCliente = taxi.getPosicaoAtual()
								.obterDistancia(
										clienteNaoAtendido.getPosicaoCliente());
						if (taxiMaisProximo == null
								|| distanciaTaxiCliente < distanciaMaisProxima) {
							distanciaMaisProxima = distanciaTaxiCliente;
							taxiMaisProximo = taxi;
						}
					}
				}

				if (taxiMaisProximo != null) {

					taxiMaisProximo.setOcupado(true);
					ambienteUI.atualizarUI();

					clientesNaoAtendidos.remove(clienteNaoAtendido);

					AID taxiId = idTaxisRegistrados.get(taxiMaisProximo
							.getIdentificacao());

					ACLMessage reqInformeBuscaCliente = new ACLMessage(
							ACLMessage.INFORM);
					reqInformeBuscaCliente.setSender(myAgent.getAID());
					reqInformeBuscaCliente.addReceiver(taxiId);

					ConteudoMensagemAgente conteudoMensagemAgente = new ConteudoMensagemAgente(
							TipoMensagemAgente.REQUISICAO_INFORME_BUSCA_CLIENTE
									.getCode());

					conteudoMensagemAgente
							.setConteudo(new Object[] {
									clienteNaoAtendido.getPosicaoCliente(),
									clienteNaoAtendido.getPosicaoDestino(),
									clienteNaoAtendido.getCliente()
											.getIdentificacao() });

					reqInformeBuscaCliente
							.setContent(reqInformeBuscaClienteParser
									.toString(conteudoMensagemAgente));

					myAgent.send(reqInformeBuscaCliente);

				}

			}

		}

	}

}
